#pragma once


#include "MessageIdentifiers.h"
#include "RakPeerInterface.h"
#include "RakNetStatistics.h"
#include "RakNetTypes.h"
#include "BitStream.h"
#include "PacketLogger.h"
#include <assert.h>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include "RakNetTypes.h"
#include "Kbhit.h"
#include "WindowsIncludes.h" // Sleep
#include "Gets.h"
#include "Input.h"


#include "Msg.h"
#include "ClientInterface.h"
#include "Defines.h"
class hammurabiNet
{
private:
	clientInterface* ci;
	hammurabiNet()
	{

	}

	unsigned char GetPacketIdentifier(RakNet::Packet *p)
	{
		if (p==0)
			return 255;

		if ((unsigned char)p->data[0] == ID_TIMESTAMP)
		{
			RakAssert(p->length > sizeof(RakNet::MessageID) + sizeof(RakNet::Time));
			return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)];
		}
		else
			return (unsigned char) p->data[0];
	}

	// Holds packets
	RakNet::Packet* p;
	RakNet::RakNetStatistics *rss;
	// Pointers to the interfaces of our server and client.
	// Note we can easily have both in the same program
	RakNet::RakPeerInterface *client;
	//	client->InitializeSecurity(0,0,0,0);
	//RakNet::PacketLogger packetLogger;
	//client->AttachPlugin(&packetLogger);
	char message[MESSAGE_BUFF];
	// Holds user data
	char ip[64], serverPort[30], clientPort[30];
	RakNet::SocketDescriptor* socketDescriptor;
	// GetPacketIdentifier returns this
	unsigned char packetIdentifier;
	msg gameState;
public:
	void setClient(clientInterface* clients)
	{
		ci = clients;
	}
	static hammurabiNet* getInstance()
	{
		static hammurabiNet instance;
		//if(!instance)
		//	instance = new hammurabiNet;
		return &instance;
	}

	~hammurabiNet()
	{
		if(client){
			client->Shutdown(300);
			// We're done with the network
			RakNet::RakPeerInterface::DestroyInstance(client);
			client = 0;
		}
		if(socketDescriptor)
		{
			delete socketDescriptor;
			socketDescriptor = 0;
		}
	}

	int input(char * message)
	{
		if (strcmp(message, "quit")==0)
		{
			puts("Quitting.");
		}
		else if (strcmp(message, "stat")==0)
		{

			rss=client->GetStatistics(client->GetSystemAddressFromIndex(0));
			StatisticsToString(rss, this->message, 2);
			printf("%s", this->message);
			printf("Ping=%i\n", client->GetAveragePing(client->GetSystemAddressFromIndex(0)));

		}
		else if (strcmp(message, "disconnect")==0)
		{
			printf("Enter IP to disconnect: ");
			char str[32];
			char port[32];
			strcpy_s(str,inputs(message));
			if (str[0]==0)
				strcpy_s(str,"0");
			_putch('\n');
			printf("Enter Port to disconnect from: ");
			strcpy_s(port,inputs(message));
			client->CloseConnection(RakNet::SystemAddress(ip,SERVER_PORT),true);
			_putch('\n');
			printf("Disconnecting.\n");
			ZeroMemory(ip,64);
		}
		else if (strcmp(message, "shutdown")==0)
		{
			client->Shutdown(100);
			printf("Shutdown.\n");
		}
		else if (strcmp(message, "startup")==0)
		{
			bool b = client->Startup(8,socketDescriptor, 1)==RakNet::RAKNET_STARTED;
			if (b)
				printf("Started.\n");
			else
				printf("Startup failed.\n");
		}
		else if (strcmp(message, "connect")==0)
		{
			if(ip[0])
			{
				printf("Already connected to server %s\|%s\n",ip,serverPort);
			}
			else
			{
				printf("Enter server ip: ");
				strcpy_s(ip,inputs(message));
				_putch('\n');
				if (ip[0]==0)
					strcpy_s(ip, "127.0.0.1");

				printf("Enter server port: ");
				strcpy_s(serverPort,inputs(message));
				if (serverPort[0]==0)
					strcpy_s(serverPort, "20");
				_putch('\n');
				bool b = client->Connect(ip, atoi(serverPort), "", (int) strlen(""))==RakNet::CONNECTION_ATTEMPT_STARTED;	

				if (b)
					puts("Attempting connection");
				else
				{
					puts("Bad connection attempt.");
					//exit(1);
				}
			}
		}
		else if (strcmp(message, "ping")==0)
		{
			if (client->GetSystemAddressFromIndex(0)!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
				client->Ping(client->GetSystemAddressFromIndex(0));

		}
		else if (strcmp(message, "getlastping")==0)
		{
			if (client->GetSystemAddressFromIndex(0)!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
				printf("Last ping is %i\n", client->GetLastPing(client->GetSystemAddressFromIndex(0)));
		}
		else if (strcmp(message, "sendstruct")==0)
		{
			ZeroMemory(message, MESSAGE_BUFF);
		}
		else if (strcmp(message, "newgame")==0)
		{
			char start[5];
			start[0] = ID_NEW_GAME;
			client->Send(start, (int) strlen(start)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
		}
		else
		{
			return 0;
		}
		puts("Press any key to continue...");
		_getch();
		return 1;
	}

	int init()
	{

		client=RakNet::RakPeerInterface::GetInstance();

		// Just so we can remember where the packet came from
		bool isServer;

		// Record the first client that connects to us so we can pass it to the ping function
		RakNet::SystemAddress clientID=RakNet::UNASSIGNED_SYSTEM_ADDRESS;

		// Crude interface


		// A client
		isServer=false;

		// Get our input
		puts("Enter the client port to listen on");
		strcpy_s(clientPort,inputs(message));
		if (clientPort[0]==0)
			strcpy_s(clientPort, "10");
		_putch('\n');

		puts("Enter IP to connect to");
		strcpy_s(ip,inputs(message));
		client->AllowConnectionResponseIPMigration(false);
		if (ip[0]==0)
			strcpy_s(ip, "127.0.0.1");
		_putch('\n');

		puts("Enter Port to connect to");
		strcpy_s(serverPort,inputs(message));
		if (serverPort[0]==0)
			strcpy_s(serverPort, "20");
		_putch('\n');
		// Connecting the client is very simple.  0 means we don't care about
		// a connectionValidationInteger, and false for low priority threads
		socketDescriptor = new RakNet::SocketDescriptor(atoi(clientPort),0);
		socketDescriptor->socketFamily=AF_INET;
		client->Startup(8,socketDescriptor, 1);
		client->SetOccasionalPing(true);

		RakNet::ConnectionAttemptResult car = client->Connect(ip, SERVER_PORT, "", (int) strlen(""));
		RakAssert(car==RakNet::CONNECTION_ATTEMPT_STARTED);

		printf("\nMy IP addresses:\n");
		unsigned int i;
		for (i=0; i < client->GetNumberOfAddresses(); i++)
		{
			printf("%i. %s\n", i+1, client->GetLocalIP(i));
		}

		printf("My GUID is %s\n", client->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS).ToString());
		puts("'quit' to quit. 'stat' to show stats. 'ping' to ping.\n'disconnect' to disconnect. 'connect' to reconnnect. Type to talk.");
		puts("Press any key to continue...");
		gameState.setVoid();
		_getch();
		return 0;
	}
	void serverMsg(msg m)
	{
		m.convertTo(message);
		client->Send(message, strlen(message)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
	}

	void update()
	{
		for (p=client->Receive(); p; client->DeallocatePacket(p), p=client->Receive())
		{
			RakNet::BitStream  bit;
			printf("Message recieved: %s\n",p->data);
			// We got a packet, get the identifier with our handy function
			packetIdentifier = GetPacketIdentifier(p);
			
			// Check if this is a network message packet
			switch (packetIdentifier)
			{
			case ID_DISCONNECTION_NOTIFICATION:
				// Connection lost normally
				printf("ID_DISCONNECTION_NOTIFICATION\n");
				break;
			case ID_ALREADY_CONNECTED:
				// Connection lost normally
				printf("ID_ALREADY_CONNECTED with guid %"PRINTF_64_BIT_MODIFIER"u\n", p->guid);
				break;
			case ID_INCOMPATIBLE_PROTOCOL_VERSION:
				printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
				break;
			case ID_REMOTE_DISCONNECTION_NOTIFICATION: // Server telling the clients of another client disconnecting gracefully.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n"); 
				break;
			case ID_REMOTE_CONNECTION_LOST: // Server telling the clients of another client disconnecting forcefully.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_CONNECTION_LOST\n");
				break;
			case ID_REMOTE_NEW_INCOMING_CONNECTION: // Server telling the clients of another client connecting.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n");
				break;
			case ID_CONNECTION_BANNED: // Banned from this server
				printf("We are banned from this server.\n");
				break;			
			case ID_CONNECTION_ATTEMPT_FAILED:
				printf("Connection attempt failed\n");
				break;
			case ID_NO_FREE_INCOMING_CONNECTIONS:
				// Sorry, the server is full.  I don't do anything here but
				// A real app should tell the user
				printf("ID_NO_FREE_INCOMING_CONNECTIONS\n");
				break;

			case ID_INVALID_PASSWORD:
				printf("ID_INVALID_PASSWORD\n");
				break;

			case ID_CONNECTION_LOST:
				// Couldn't deliver a reliable packet - i.e. the other system was abnormally
				// terminated
				printf("ID_CONNECTION_LOST\n");
				break;

			case ID_CONNECTION_REQUEST_ACCEPTED:
				// This tells the client they have connected
				printf("ID_CONNECTION_REQUEST_ACCEPTED to %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString());
				printf("My external address is %s\n", client->GetExternalID(p->systemAddress).ToString(true));
				break;
			case ID_CONNECTED_PING:
			case ID_UNCONNECTED_PING:
				printf("Ping from %s\n", p->systemAddress.ToString(true));
				break;
			case ID_GAME_STATE:
				gameState.convert((char*)p->data);
				break;
			default:
				// It's a client, so just show the message
				printf("SERVER MESSAGE:\n%s\nPress any key to continue...",p->data);
				_getch();
				break;
			}
			if(!gameState.isEmpty())
			{
				ci->clientMessage(gameState);
				gameState.setVoid();
			}
		}
	}
};